002.提示工程基础:Prompt Templates

出处:提示工程基础:Prompt Templates

原作者:怕浪猫


提示工程(Prompt Engineering)是连接“用户需求”与“模型输出”的关键桥梁,直接决定了模型回答的质量和准确性。

在实际开发中,我们不会每次都手动编写完整提示(比如客服话术、问答模板、文案生成指令),而是将重复的提示结构模板化——这就是 LangChain 中的 Prompt Templates(提示模板)。

1. 直观对比:模板化 vs 非模板化

方式 代码示例 优势 劣势
非模板化 prompt = f"你是客服,用户问题是:{user_question},请简洁回答,不超过2句话。" 简单直接,适合一次性场景 重复编写、格式易乱、变量管理繁琐
模板化 prompt = PromptTemplate.from_template("你是客服,用户问题是:{user_question},请简洁回答,不超过2句话。") 可复用、标准化、易维护 需提前定义模板,适合重复场景

结论:开发中只要涉及“重复提示结构”,都建议使用 Prompt Templates,尤其是客服、问答、文案生成等场景。

2. 使用 PromptTemplate 构建静态提示

PromptTemplate 是 LangChain 中最基础的提示模板类,用于构建静态提示——模板中的固定文本不变,只有动态变量需要替换,适合单轮、简单场景(如纯文本生成、简单问答)。

核心特点:输入是纯文本模板,输出是格式化后的纯文本,适配 LLM(纯文本交互)和 ChatModel(消息内容)。

1. 核心用法(3 种创建方式)

LangChain 提供 3 种创建 PromptTemplate 的方式,按需选择,最常用的是 from_template()(简洁高效)。

1. 基础方式:from_template()(推荐)

from langchain_core.prompts import PromptTemplate

# 1. 定义模板({变量名} 表示动态变量)
template = "你是一名文案助手,请为{product}撰写一句宣传语,突出{feature}优势,简洁有力。"

# 2. 创建PromptTemplate实例
prompt_template = PromptTemplate.from_template(template)

# 3. 注入变量,生成完整提示
prompt = prompt_template.format(product="LangChain教程", feature="实战导向、通俗易懂")

print("生成的提示:", prompt)

2. 显式指定变量:构造函数

当模板变量较多时,可显式指定变量列表,避免遗漏或错误:

from langchain_core.prompts import PromptTemplate

# 显式指定variables参数,明确模板中的变量
prompt_template = PromptTemplate(
    template="用户问题:{question}\n要求:{requirement}\n回答:",
    variables=["question", "requirement"]  # 显式指定变量列表
)

# 注入变量
prompt = prompt_template.format(
    question="什么是PromptTemplate?",
    requirement="简洁明了,不超过3句话"
)

print(prompt)

3. 从文件加载模板:from_file()

当模板内容较长(如复杂的客服话术、少样本示例),可将模板写入文件(如 txt),再通过 from_file() 加载,便于维护:

from langchain_core.prompts import PromptTemplate

# 1. 新建prompt_template.txt文件,写入模板内容:
# 你是一名技术面试官,针对{position}岗位,提出1个{difficulty}难度的面试题,要求包含考察点。

# 2. 从文件加载模板
prompt_template = PromptTemplate.from_file("prompt_template.txt")

# 3. 注入变量
prompt = prompt_template.format(position="Python开发", difficulty="中等")

print(prompt)

2. 核心注意事项

3. 实战小技巧:partial_format() 部分变量注入

当模板中有多个变量,且部分变量固定(如“角色设定”)、部分变量动态(如“用户问题”)时,可使用 partial_format() 先注入固定变量,生成新的模板,后续只需注入动态变量:

from langchain_core.prompts import PromptTemplate

# 模板:包含固定变量(role)和动态变量(question)
template = "你是{role},请回答用户问题:{question},要求简洁易懂。"
prompt_template = PromptTemplate.from_template(template)

# 先注入固定变量(role),生成新模板
partial_template = prompt_template.partial_format(role="LangChain开发助手")

# 后续只需注入动态变量(question)
prompt1 = partial_template.format(question="什么是PromptTemplate?")
prompt2 = partial_template.format(question="如何创建PromptTemplate?")

print("prompt1:", prompt1)
print("prompt2:", prompt2)

3. ChatPromptTemplate 与多轮对话结构

上一节的 PromptTemplate 适用于“纯文本提示”,而 ChatModel 的交互是“消息列表”(SystemMessage、HumanMessage、AIMessage),因此 LangChain 提供了 ChatPromptTemplate ——专门用于构建多轮对话提示模板,适配 ChatModel 的交互格式。

核心区别:PromptTemplate 生成纯文本,ChatPromptTemplate 生成消息列表(可直接传递给 ChatModel 的 invoke()方法)。

1. 核心用法:创建多轮对话模板

ChatPromptTemplate 由多个 ChatMessageTemplate 组成,每个 ChatMessageTemplate 对应一种消息类型(System、Human、AI),示例如下:

from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os

load_dotenv()

# 1. 定义各消息类型的模板
# 系统消息模板(固定角色设定)
system_template = "你是{role},回答用户问题时,需包含1个简单代码示例,简洁明了。"
system_message_prompt = SystemMessagePromptTemplate.from_template(system_template)

# 用户消息模板(动态用户问题)
human_template = "用户问题:{question}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

# 2. 组合成ChatPromptTemplate(多轮对话结构)
chat_prompt_template = ChatPromptTemplate.from_messages([
    system_message_prompt,  # 系统消息(第1条)
    human_message_prompt    # 用户消息(第2条)
])

# 3. 注入变量,生成消息列表
messages = chat_prompt_template.format_messages(
    role="LangChain开发助手",
    question="如何使用ChatPromptTemplate?"
)

# 4. 调用ChatModel(直接传入生成的消息列表)
chat_model = ChatOpenAI(
    model_name="gpt-3.5-turbo",
    api_key=os.getenv("OPENAI_API_KEY"),
    temperature=0.7
)

response = chat_model.invoke(messages)
print("模型响应:", response.content)

2. 简化写法:from_template() 快速创建

对于简单的多轮对话模板,可使用 ChatPromptTemplate.from_template() 快速创建,无需单独定义每个 ChatMessageTemplate:

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os

load_dotenv()

# 简化写法:用列表定义消息类型和模板,格式:["消息类型: 模板内容"]
chat_prompt_template = ChatPromptTemplate.from_template([
    "system: 你是{role},回答简洁,不超过2句话。",
    "human: 用户问题:{question}"
])

# 生成消息列表
messages = chat_prompt_template.format_messages(
    role="LangChain助手",
    question="ChatPromptTemplate和PromptTemplate的区别是什么?"
)

# 调用模型
chat_model = ChatOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
print(chat_model.invoke(messages).content)

说明:消息类型支持 3 种简写:systemhumanai,分别对应 SystemMessage、HumanMessage、AIMessage。

3. 多轮对话扩展(包含 AI 消息)

ChatPromptTemplate 支持包含 AI 消息模板,用于构建复杂的多轮对话场景(如历史对话复用):

from langchain_core.prompts import ChatPromptTemplate

# 多轮对话模板:系统消息→用户消息→AI消息→新用户消息
chat_prompt_template = ChatPromptTemplate.from_template([
    "system: 你是数学老师,回答问题时步骤清晰。",
    "human: {question1}",
    "ai: {answer1}",
    "human: {question2}"
])

# 注入变量,生成完整消息列表
messages = chat_prompt_template.format_messages(
    question1="1+1等于多少?",
    answer1="1+1=2,因为两个1相加的结果是2。",
    question2="那2+3等于多少?"
)

for msg in messages:
    print(f"【{msg.type}】: {msg.content}")

运行结果可看到完整的多轮对话消息结构,适合需要复用历史对话的场景(如聊天机器人)。

4. 动态变量注入与格式化(f-string 替代方案)

提示模板的核心功能是“动态变量注入”——将用户输入、业务数据等动态内容,替换到模板的变量中,生成完整提示。

1. 核心优势:模板格式化 vs f-string

对比维度 LangChain 模板格式化 f-string 拼接
变量校验 自动校验变量是否齐全,缺失时报错,避免遗漏 无校验,变量缺失时抛出异常,不易排查
复杂场景支持 支持部分注入、条件格式化、列表格式化 需手动处理复杂逻辑,代码繁琐
可维护性 模板与代码分离,便于修改和复用 提示与代码混合,修改繁琐
安全性 自动转义特殊字符,避免注入风险 需手动处理特殊字符,存在注入风险

2. 常用格式化技巧(实战必备)

1. 列表变量格式化(批量注入多个值)

当变量是列表(如多个产品、多个问题)时,模板可自动将列表格式化为自然语言,无需手动循环拼接:

from langchain_core.prompts import PromptTemplate

# 模板:包含列表变量{products}
template = "请为以下产品各写一句宣传语:{products},要求每句不超过10字。"
prompt_template = PromptTemplate.from_template(template)

# 注入列表变量
prompt = prompt_template.format(
    products=["LangChain教程", "Python实战", "AI提示工程"]
)

print(prompt)

运行结果:请为以下产品各写一句宣传语:LangChain教程、Python实战、AI提示工程,要求每句不超过10字。

2. 条件格式化(根据变量值动态调整提示)

通过 if-else 语法,根据变量值动态调整提示内容,适配不同场景:

from langchain_core.prompts import PromptTemplate

# 模板:条件格式化(根据{difficulty}调整提示)
template = """
你是一名出题老师,为{subject}学科出题:
{% if difficulty == '简单' %}
出3道基础题,侧重概念理解。
{% elif difficulty == '中等' %}
出2道中档题,侧重应用能力。
{% else %}
出1道难题,侧重综合能力。
{% endif %}
"""
prompt_template = PromptTemplate.from_template(template)

# 测试不同条件
prompt1 = prompt_template.format(subject="数学", difficulty="简单")
prompt2 = prompt_template.format(subject="语文", difficulty="困难")

print("简单难度:", prompt1)
print("困难难度:", prompt2)

3. 特殊字符转义(自动处理)

当变量中包含特殊字符(如{、}、引号)时,模板会自动转义,避免格式化报错,而 f-string 需要手动处理:

from langchain_core.prompts import PromptTemplate

# 变量中包含特殊字符{ }
template = "用户输入的内容是:{user_input},请解析其中的关键信息。"
prompt_template = PromptTemplate.from_template(template)

# 注入包含特殊字符的变量
prompt = prompt_template.format(user_input="我喜欢LangChain,它的{PromptTemplate}很实用")

print(prompt)

运行结果:用户输入的内容是:我喜欢LangChain,它的{PromptTemplate}很实用,请解析其中的关键信息。(模板自动转义,无报错)。

5. 少样本提示(Few-shot Prompting)模板设计

少样本提示(Few-shot Prompting)是提示工程的核心技巧之一——通过在提示中加入“少量示例”,让模型快速理解任务要求、输出格式,提升回答的准确性,尤其适合复杂任务(如分类、总结、翻译)。

LangChain 的提示模板支持直接嵌入少样本示例,无需额外编写复杂逻辑,下面介绍两种常用的少样本模板设计方式。

1. 基础方式:直接嵌入示例(适合少量示例)

将少样本示例直接写入模板,作为固定内容,动态变量仅注入用户输入,适合示例数量少(1~3 个)的场景:

from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os

load_dotenv()

# 少样本模板:包含2个示例,动态变量{user_input}
template = """
请将用户输入的文本分类为「正面」「负面」「中性」,示例如下:
示例1:输入:我很喜欢这款产品,体验很好 → 分类:正面
示例2:输入:这款产品质量很差,不推荐 → 分类:负面
用户输入:{user_input} → 分类:
"""
prompt_template = PromptTemplate.from_template(template)

# 注入用户输入
prompt = prompt_template.format(user_input="这款产品中规中矩,没有惊喜也没有失望")

# 调用模型
chat_model = ChatOpenAI(api_key=os.getenv("OPENAI_API_KEY"), temperature=0.1)
print("分类结果:", chat_model.invoke([{"role": "user", "content": prompt}]).content)

运行结果:中性,模型通过示例快速理解了分类规则。

2. 进阶方式:使用 FewShotPromptTemplate(适合多示例)

当示例数量较多(3 个以上)时,可使用 FewShotPromptTemplate,将“示例列表”和“模板”分离,便于示例的管理和复用:

from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate

# 1. 定义示例列表(可单独维护,便于修改)
examples = [
    {"input": "我很喜欢这款手机,续航超给力", "output": "正面"},
    {"input": "这款手机发热严重,体验很差", "output": "负面"},
    {"input": "这款手机外观一般,价格适中", "output": "中性"},
    {"input": "手机拍照效果不错,但系统有点卡顿", "output": "中性"}
]

# 2. 定义单个示例的模板
example_template = "输入:{input} → 分类:{output}"
example_prompt = PromptTemplate.from_template(example_template)

# 3. 定义少样本模板(组合示例和用户输入模板)
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,  # 示例列表
    example_prompt=example_prompt,  # 单个示例模板
    prefix="请将用户输入的文本分类为「正面」「负面」「中性」,示例如下:",  # 前缀(任务说明)
    suffix="用户输入:{user_input} → 分类:",  # 后缀(用户输入和输出格式)
    input_variables=["user_input"]  # 动态变量
)

# 4. 注入变量,生成提示
prompt = few_shot_prompt.format(user_input="这款手机续航一般,拍照还行")
print("少样本提示:", prompt)

3. 少样本模板设计技巧(必看)

6. 示例选择器(ExampleSelector)自动匹配上下文

上一节的少样本提示,示例是固定的——无论用户输入是什么,都会使用相同的示例。但在实际场景中,不同的用户输入需要匹配不同的示例(如用户输入是“手机续航”,应匹配与续航相关的示例;输入是“拍照”,应匹配与拍照相关的示例)。

LangChain 的 ExampleSelector(示例选择器)可解决这一问题——根据用户输入的上下文,自动从示例列表中选择最相关的示例,注入到提示模板中,让少样本提示更精准、更高效。

1. 核心示例选择器:SemanticSimilarityExampleSelector

最常用的示例选择器是 SemanticSimilarityExampleSelector(语义相似度示例选择器),核心逻辑:将用户输入和示例进行语义嵌入(Embedding),计算相似度,选择相似度最高的 N 个示例,适合大多数场景。

from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from dotenv import load_dotenv
import os

load_dotenv()

# 1. 定义示例列表(包含不同场景的示例)
examples = [
    {"input": "手机续航超给力", "output": "正面"},
    {"input": "手机发热严重", "output": "负面"},
    {"input": "拍照效果不错", "output": "正面"},
    {"input": "系统有点卡顿", "output": "负面"},
    {"input": "价格适中", "output": "中性"}
]

# 2. 定义单个示例模板
example_template = "输入:{input} → 分类:{output}"
example_prompt = PromptTemplate.from_template(example_template)

# 3. 初始化示例选择器(语义相似度)
example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples=examples,  # 示例列表
    embeddings=OpenAIEmbeddings(api_key=os.getenv("OPENAI_API_KEY")),  # 嵌入模型
    vectorstore_cls=Chroma,  # 向量数据库(用于存储示例嵌入)
    k=2  # 选择相似度最高的2个示例
)

# 4. 定义少样本模板(结合示例选择器)
few_shot_prompt = FewShotPromptTemplate(
    example_selector=example_selector,  # 替换固定examples为示例选择器
    example_prompt=example_prompt,
    prefix="请将用户输入的文本分类为「正面」「负面」「中性」,参考相关示例:",
    suffix="用户输入:{user_input} → 分类:",
    input_variables=["user_input"]
)

# 5. 测试:不同用户输入,自动选择不同示例
prompt1 = few_shot_prompt.format(user_input="手机续航很差")
prompt2 = few_shot_prompt.format(user_input="拍照很模糊")

print("=== 用户输入:手机续航很差 ===")
print(prompt1)
print("\n=== 用户输入:拍照很模糊 ===")
print(prompt2)

运行结果可看到:

2. 其他常用示例选择器

除了语义相似度选择器,LangChain 还提供其他示例选择器,适配不同场景:

3. 实战建议

示例选择器适合“示例数量多、场景复杂”的少样本提示场景,能大幅提升提示的精准度;如果示例数量少(1~2 个),直接嵌入示例即可,无需使用示例选择器(避免过度复杂)。

7. 提示模板的序列化与版本管理

在实际项目开发中,提示模板会不断迭代优化(如修改示例、调整提示格式),需要进行版本管理;同时,为了便于团队协作、部署上线,需要将模板序列化(保存为文件),后续可直接加载使用,无需重新定义。

LangChain 支持多种序列化格式(JSON、YAML),操作简单,可直接集成到项目的版本管理(如 Git)中。

1. 模板序列化(保存为文件)

LangChain 的提示模板(PromptTemplate、ChatPromptTemplate、FewShotPromptTemplate)均支持 save() 方法,可将模板保存为 JSON 或 YAML 文件,示例如下:

from langchain_core.prompts import PromptTemplate, ChatPromptTemplate

# 1. 序列化PromptTemplate(保存为JSON)
prompt_template = PromptTemplate.from_template(
    "你是{role},请回答用户问题:{question},要求简洁明了。"
)
# 保存为JSON文件
prompt_template.save("prompt_template.json")

# 2. 序列化ChatPromptTemplate(保存为YAML)
chat_prompt_template = ChatPromptTemplate.from_template([
    "system: 你是{role},回答包含代码示例。",
    "human: {question}"
])
# 保存为YAML文件(推荐,格式更简洁)
chat_prompt_template.save("chat_prompt_template.yaml")

2. 模板反序列化(从文件加载)

通过 load_prompt() 方法,可从 JSON/YAML 文件中加载模板,无需重新定义,示例如下:

from langchain_core.prompts import load_prompt

# 1. 加载JSON格式的PromptTemplate
loaded_prompt = load_prompt("prompt_template.json")
# 注入变量使用
prompt = loaded_prompt.format(role="LangChain助手", question="什么是模板序列化?")
print("加载的PromptTemplate:", prompt)

# 2. 加载YAML格式的ChatPromptTemplate
loaded_chat_prompt = load_prompt("chat_prompt_template.yaml")
messages = loaded_chat_prompt.format_messages(role="开发助手", question="如何加载模板?")
print("\n加载的ChatPromptTemplate:")
for msg in messages:
    print(f"【{msg.type}】: {msg.content}")

3. 版本管理建议

结合 Git 进行模板版本管理,核心步骤:

  1. 将序列化后的模板文件(JSON/YAML)纳入 Git 管理;
  2. 每次修改模板后,提交代码时添加清晰的备注(如“优化客服话术模板,增加示例”);
  3. 如需回滚到历史版本,直接从 Git 中拉取对应版本的模板文件,重新加载即可;
  4. 团队协作时,统一模板文件路径,避免重复创建,确保所有人使用相同版本的模板。